package com.harry.configuration.postman;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;
import com.harry.decoupling.defi.tool.DataExchange;
import com.harry.utility.InnerUtils;
import com.harry.utility.PoEntityHelper;
import com.harry.utility.query.InnerCriteria;
import com.harry.utility.query.InnerCriterion;
import com.harry.utility.query.InnerOrderBy;
import com.harry.utility.query.InnerProjection;

/**
 * postman 格式的API生成接口
 */
@RestController
@RequestMapping("/statics")
public class PostManConfig implements InitializingBean {

	@Autowired
	private DataExchange de;

	@GetMapping("/postMan.json")
	public Object get() throws Exception {
		Map<String, Object> json = InnerUtils.mapObject();
		{
			Map<String, Object> info;
			json.put("info", info = InnerUtils.mapObject());
			info.put("name", "APIs");
			info.put("schema", "https://schema.getpostman.com/json/collection/v2.1.0/collection.json");
			info.put("_postman_id", UUID.randomUUID().toString());
		}
		List<Object> itemRoot;
		json.put("item", itemRoot = InnerUtils.listObj());
		{
			itemRoot.add(FILECONTENT_FOLDER);
		}
		{
			Set<Class<?>> classes = new TreeSet<Class<?>>(
					(o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getSimpleName(), o2.getSimpleName()));
			classes.addAll(InnerUtils.getAllEntities());
			for (Class<?> clazz : classes) {
				Map<String, Object> item;
				itemRoot.add(item = InnerUtils.mapObject());
				item.put("name", instanceName(clazz));
				List<Object> srcItems;
				item.put("item", srcItems = InnerUtils.listObj());
				for (int i = 0; i < actions.length; i++) {
					Map<String, Object> srcItem;
					srcItems.add(srcItem = InnerUtils.mapObject());
					srcItem.put("name", instanceName(clazz) + "-" + actions[i]);
					Map<String, Object> reqObj;
					srcItem.put("request", reqObj = InnerUtils.mapObject());
					reqObj.put("method", methods[i]);
					Map<String, Object> urlObj;
					reqObj.put("url", urlObj = InnerUtils.mapObject());
					urlObj.put("host", new String[] { SM_HOST });
					urlObj.put("path", pathJoin(clazz, i));
					urlObj.put("raw", rawJson(clazz, i));
					if (!"POST".equals(methods[i])) {
						Map<String, Object> variable;
						urlObj.put("variable", variable = InnerUtils.mapObject());
						variable.put("key", "id");
						variable.put("value", "");
					}
					if (i == 0 || i == 5) {
						urlObj.put("query", QUERYTOKEN);
						reqObj.put("header", new Object[] {});
					} else {
						reqObj.put("header", HEADERTOKEN);
						Map<String, Object> body;
						reqObj.put("body", body = InnerUtils.mapObject());
						body.put("mode", "raw");
						Map<String, Object> options = InnerUtils.mapObject();
						PoEntityHelper.writeValueByProperty(options, "raw.language", "json");
						body.put("options", options);
						if (i == 1 || i == 6) {
							body.put("raw", CRITERIA);
						} else if (i == 2 || i == 3 || i == 4) {
							InnerUtils.tryCatch(() -> {
								@SuppressWarnings("unchecked")
								Map<String, Object> raw = de.parse(
										de.serialize(fillEmpty(clazz.getDeclaredConstructor().newInstance())),
										new LinkedHashMap<String, Object>().getClass());
								raw.remove("user");
								raw.remove("source");
								raw.remove("id");
								raw.remove("createTime");
								Set<String> keys4remove = new HashSet<>();
								raw.keySet().stream().filter(s -> s.indexOf("Str") > 0 && s.indexOf("Str") == s.length() - 3)
										.forEach(s -> keys4remove.add(s));
								keys4remove.stream().forEach(s -> raw.remove(s));
								body.put("raw", JSON.toJSONString(raw, true));
							});
						}
					}
				}
			}
		}
		itemRoot.add(BATCH);
		return de.serialize(json);
	}

	private static Class<?>[] pagckageClasses = new Class<?>[] { byte.class, Byte.class, // e
			short.class, Short.class, int.class, Integer.class, long.class, Long.class, // e
			char.class, Character.class, // e
			float.class, Float.class, double.class, Double.class, BigDecimal.class, // e
			boolean.class, Boolean.class// e
	};

	private static Object[] packageValues = new Object[] { 0x00, 0x00, // e
			0, 0, 0, 0, 0l, 0L, // e
			'0', '0', // e
			0.0, 0.0, 0.0, 0.0, new BigDecimal("0"), // e
			false, false };

	private static Map<Class<?>, Object> packageClassesMap = new HashMap<>();
	static {
		for (int i = 0; i < pagckageClasses.length; i++) {
			packageClassesMap.put(pagckageClasses[i], packageValues[i]);
		}
	}

	private static Object fillEmpty(Object obj) throws Exception {
		PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(obj.getClass());
		for (PropertyDescriptor temp : props) {
			Object value = temp.getReadMethod().invoke(obj);
			if (value != null) {
				continue;
			} else if (temp.getPropertyType().isArray()) {
				Object array;
				temp.getWriteMethod().invoke(obj, array = Array.newInstance(temp.getPropertyType().getComponentType(), 1));
				Array.set(array, 0, temp.getPropertyType().getComponentType().getDeclaredConstructor().newInstance());
			} else if (Collection.class.isAssignableFrom(temp.getPropertyType())) {
				Collection<Object> collection;
				temp.getWriteMethod().invoke(obj, collection = new HashSet<>());
				Method readMethod = temp.getReadMethod();
				ParameterizedType returnType = (ParameterizedType) readMethod.getGenericReturnType();
				Class<?> genericType = (Class<?>) returnType.getActualTypeArguments()[0];
				collection.add(genericType.getDeclaredConstructor().newInstance());
			} else if (packageClassesMap.containsKey(temp.getPropertyType())) {
				temp.getWriteMethod().invoke(obj, packageClassesMap.get(temp.getPropertyType()));
			} else {
				temp.getWriteMethod().invoke(obj, temp.getReadMethod().getReturnType().getDeclaredConstructor().newInstance());
			}
		}
		return obj;
	}

	private Object rawJson(Class<?> clazz, int i) {
		return "/" + SM_HOST + "/" + RESOURCE + "/" + instanceName(clazz) + "/" + actions[i]
				+ (!"POST".equals(methods[i]) ? "/:id" : "");
	}

	private String[] pathJoin(Class<?> clazz, int i) {
		if (!"POST".equals(methods[i])) {
			return new String[] { RESOURCE, instanceName(clazz), actions[i], ":id" };
		} else {
			return new String[] { RESOURCE, instanceName(clazz), actions[i] };
		}
	};

	private static String instanceName(Class<?> cls) {
		return cls.getSimpleName().substring(0, 1).toLowerCase() + cls.getSimpleName().substring(1);
	}

	private String[] actions;

	private String[] methods;

	private String SM_HOST;

	private String RESOURCE;

	private Object PARAM_TOKEN;

	private Object PARAM_APPLI;

	private Object[] QUERYTOKEN;

	private Object[] HEADERTOKEN;

	private Map<String, Object> FILECONTENT_FOLDER;

	private Map<String, Object> BATCH;

	private Object CRITERIA;

	@Override
	public void afterPropertiesSet() throws Exception {
		actions = new String[] { "getone", /**/"getlist", /**/"post", "patch", "delete", /**/"getexcel" };
		methods = new String[] { "GET", /*------*/"POST", /**/"POST", "PATCH", "DELETE", /*---*/"POST" };
		SM_HOST = "{{SM_HOST}}";
		RESOURCE = "resource";
		PARAM_TOKEN = de.parseMap("{\"key\": \"token\",\"type\": \"text\",\"value\": \"{{SM_TOKEN}}\"}");
		PARAM_APPLI = de.parseMap(
				"{\"key\": \"Content-Type\",\"name\": \"Content-Type\",\"type\": \"text\",\"value\": \"application/json\"}");
		QUERYTOKEN = new Object[] { PARAM_TOKEN };
		HEADERTOKEN = new Object[] { PARAM_TOKEN, PARAM_APPLI };
		FILECONTENT_FOLDER = de.parseMap(
				"{\"name\":\"innerFileContent\",\"item\":[{\"name\":\"innerFileContent-getone\",\"request\":{\"method\":\"GET\",\"header\":[],\"url\":{\"raw\":\"{{SM_HOST}}/resource/innerFileContent/getone/:id?token={{SM_TOKEN}}\",\"host\":[\"{{SM_HOST}}\"],\"path\":[\"resource\",\"innerFileContent\",\"getone\",\":id\"],\"query\":[{\"key\":\"token\",\"value\":\"{{SM_TOKEN}}\"}],\"variable\":[{\"key\":\"id\",\"value\":\"\"}]}},\"response\":[]},{\"name\":\"innerFileContent-post\",\"request\":{\"method\":\"POST\",\"header\":[{\"key\":\"token\",\"value\":\"{{SM_TOKEN}}\",\"type\":\"text\"}],\"body\":{\"mode\":\"formdata\",\"formdata\":[{\"key\":\"file\",\"type\":\"file\",\"src\":[]}]},\"url\":{\"raw\":\"{{SM_HOST}}/resource/innerFileContent/post\",\"host\":[\"{{SM_HOST}}\"],\"path\":[\"resource\",\"innerFileContent\",\"post\"]}},\"response\":[]},{\"name\":\"innerFileContent-delete\",\"request\":{\"method\":\"DELETE\",\"header\":[],\"url\":{\"raw\":\"{{SM_HOST}}/resource/innerFileContent/delete/:id?token={{SM_TOKEN}}\",\"host\":[\"{{SM_HOST}}\"],\"path\":[\"resource\",\"innerFileContent\",\"delete\",\":id\"],\"query\":[{\"key\":\"token\",\"value\":\"{{SM_TOKEN}}\"}],\"variable\":[{\"key\":\"id\",\"value\":\"\"}]}},\"response\":[]}]}");
		BATCH = de.parseMap(
				"{\"name\":\"Any-batch\",\"request\":{\"method\":\"POST\",\"header\":[{\"key\":\"Content-Type\",\"name\":\"Content-Type\",\"value\":\"application/json\",\"type\":\"text\"},{\"key\":\"token\",\"value\":\"{{SM_TOKEN}}\",\"type\":\"text\"}],\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n    \\\"actions\\\": [\\n        {\\n        \\t\\\"resourceName\\\":\\\"smBuyerSupplier\\\",\\n            \\\"method\\\": \\\"patch\\\",\\n            \\\"params\\\": {\\n                \\\"id\\\": \\\"2c9f86bb6b972773016b97301b3e0003\\\"\\n            }\\n        }\\n    ]\\n}\"},\"url\":{\"raw\":\"{{SM_HOST}}/resource/:resourceName/batch\",\"host\":[\"{{SM_HOST}}\"],\"path\":[\"resource\",\":resourceName\",\"batch\"],\"variable\":[{\"key\":\"resourceName\",\"value\":\"any\"}]}},\"response\":[]}");
		CRITERIA = JSON.toJSONString(InnerUtils.mapObject(// e
				"criteria", new InnerCriteria(// e
						new InnerCriterion[] { // e
								new InnerCriterion("and",
										new InnerCriterion[] { new InnerCriterion("gt", "createTime", new Date()),
												new InnerCriterion("gt", "createTime", new Date()) }), // e
								new InnerCriterion("or",
										new InnerCriterion[] { new InnerCriterion("gt", "createTime", new Date()),
												new InnerCriterion("gt", "createTime", new Date()) }), // e
								new InnerCriterion("gt", "createTime", new Date()) // e
						}// e
						, new InnerProjection[] { new InnerProjection(null, "id"), // e
								new InnerProjection("count", "id"), // e
								new InnerProjection("group", "id"), // e
								new InnerProjection("sum", "id"), // e
								new InnerProjection("count(id=1)", "id") }// e
						, new InnerOrderBy[] { new InnerOrderBy("desc", "createTime") }) // e
				, "page", InnerUtils.mapObject("pageNo", 1, "length", 10)), true);
	}
}
